/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License specifically applicable to
 * this software at http://developer.yahoo.com/yslow/license.txt, or the general
 * form at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is: YSlow
 *
 * The Initial Developer of the Original Code is Yahoo! Inc.
 *
 * Copyright (C) 2007, Yahoo! Inc. All Rights Reserved.
 *
 * Contributor(s):
 * ***** END LICENSE BLOCK ***** */

// Component Class object in the YSLOW namespace.
YSLOW.Component = function(compType, compUrl, peelerObj) {
	this.type = compType;
	this.url = compUrl;
	this.peelerObj = peelerObj; // every Component object will be linked to the Peeler object
}

YSLOW.Component.columns_fields = ["type", "url", "expires", "compressed", "responsetime", "content-length", "etag"];

YSLOW.Component.getCompVal = function(compObj, key) {
    dprint(9, "YSLOW.Component.getCompVal: enter function");
	if ( compObj[key] )
		return compObj[key];
	else
		return "";
}


YSLOW.Component.setCompVal = function(compObj, key, val) {
    dprint(9, "YSLOW.Component.setCompVal: enter function");
	compObj[key] = val;
}

// This function will check if the components are available from Firebug's Net Panel.
// If they are, then we will search for the right one and return it's index,
// so that we can fetch the component details accordingly.
YSLOW.Component.prototype.getComponentFromNetPanel = function(url) {
	var index = undefined;
	for ( var i=0; i < FirebugContext.netProgress.files.length ; i++ ) {
		if ( url == FirebugContext.netProgress.files[i].href ) {
			index = i;
		}
	}
	//We will always return the last entry that matches the URL.
	//For some special cases, we can have 2 entries for the same URL, where the first is messed up while the second one works.
	return index;
}

//This function will return a list of response headers for a particular component from the net panel.
YSLOW.Component.prototype.getAllResponseHeadersFromNetPanel = function (file) {
	var headerString = "";
	if ( "undefined" != typeof(file.responseHeaders) ) {
		var headersLength = file.responseHeaders.length;
		var i=0;
		if ( typeof(headersLength) != "undefined" ) {

			//Lets loop over all the headers.
			for ( i=0; i<headersLength ; i++ ) {
				//Don't append new line when this entry is the last one.
				if ( i != (headersLength -1) ) {
					headerString += file.responseHeaders[i].name + ": " + file.responseHeaders[i].value + "\n";
				}
				else {
					headerString += file.responseHeaders[i].name + ": " + file.responseHeaders[i].value ;
				}
			}
		}
		else {
			dprint(5,"Response headers missing for :\n"+file.href);
		}
	}
	else {
		dprint(5,"Failed while getting the response headers for :\n"+file.href);
	}

	dprint(5, "Component.getAllResponseHeadersFromNetPanel: got headers for :\n" + file.href);
	return headerString;
}


YSLOW.Component.prototype.getResponseText = function(file) {
	return file.responseText ? file.responseText : FirebugContext.getPanel('net').context.sourceCache.loadText(file.href);
}


YSLOW.Component.prototype.fetchComponentDetails = function() {
	// Save this so we can match up the details with the component in the async callback.
	FirebugContext.yslowContext["xmlId"] = this.id;

	var url = this.url;
	var fileIndex = undefined;

    FirebugContext.yslowContext["xmlRequest"] = null;
	FirebugContext.yslowContext["xmlRequest"] = YSLOW.Utils.getXmlObject();
	if ( !FirebugContext.yslowContext["xmlRequest"] ) {
		var respAr= new Array();                             // Initialize the response array
		respAr["error"] = "ERROR: Failed to create an XML object.";
		YSLOW.Component.handleResponse(respAr);
		return;
	}

	//Before making a XHR for a component, lets check if we can get the required details from Firebug's net panel
	fileIndex = this.getComponentFromNetPanel(url);
	if ( "undefined" != typeof(fileIndex) ) {
		//We got the component from Net panel.
		var file = FirebugContext.getPanel('net').context.netProgress.files[fileIndex];

		//If the component is cached or not modified, Net panel won't store the headers for it.
		//Lets XHR these to get all the info.
		if ( file.status && file.status != "304" ) {
			FirebugContext.yslowContext["xmlStarttime"] = file.startTime;
			FirebugContext.yslowContext["xmlEndtime"] = file.endTime;
			FirebugContext.yslowContext["xmlUrl"] = url;

			respAr = {};
			respAr.responseHeaders = this.getAllResponseHeadersFromNetPanel(file);
			respAr.status = file.status;

			respAr.statusText = file.request.responseStatusText;
			respAr.responseText = this.getResponseText( file );
			FirebugContext.yslowContext.respAr = respAr;
			// Use setTimeout to give the panel time to draw the progress bars.
			setTimeout("YSLOW.Component.handleResponse(FirebugContext.yslowContext.respAr)", 1);
			return;
		}
	}

	FirebugContext.yslowContext["xmlRequest"].onreadystatechange = YSLOW.Component.xmlCallback;
	if ( -1 != url.indexOf("#") ) {
		// The url gets escaped during the request, and embedded anchors (eg, "#footnotes") cause 404s.
		url = url.substring(0, url.indexOf("#"));
	}

	FirebugContext.yslowContext["xmlUrl"] = url;
	try {
		FirebugContext.yslowContext["xmlRequest"].open("GET", url, true);  // true == async
		FirebugContext.yslowContext["xmlRequest"].setRequestHeader('User-agent', navigator.userAgent);
		// Make sure XMLHttpRequest does NOT use the browser's cache.
		// This appears to only work in IE, but I'll do it all the time just in case FF starts supporting it.
		FirebugContext.yslowContext["xmlRequest"].setRequestHeader('If-Modified-Since', 'Fri, 01 Jan 1971 00:00:00 GMT');
		FirebugContext.yslowContext["xmlStarttime"] = Number(new Date());  // do this at the VERY LAST MINUTE
		FirebugContext.yslowContext["xmlRequest"].send("");
	}
	catch(err) {
		eprint('ERROR: XMLHttpRequest failed for "' + url + '".');
		FirebugContext.yslowContext.peelerObj.doneFetching();
	}

	// If you reach here it means a success XML request was sent so flow will resume with the appropriate callback.
}


// The callback function for raw XML requests (NOT Greasemonkey).
// The raw XML callback does NOT include an object with the response data.
// This function builds that and also handles the different ready states.
YSLOW.Component.xmlCallback = function(respAr) {
	var readyState = ( respAr ? respAr.readyState : FirebugContext.yslowContext["xmlRequest"].readyState );
    if ( 4 == readyState ) {    // the request is done (altho we have a bug where a 304 resp will never reach readystate 4 and is missed here)
		FirebugContext.yslowContext["xmlEndtime"] = Number(new Date());  // do this AS EARLY AS POSSIBLE
		if ( ! respAr ) {
			// Only Greasemonkey callback passes an object. Ow, we have to build it.
			var req = FirebugContext.yslowContext["xmlRequest"];
			respAr = {};
			respAr.responseHeaders = req.getAllResponseHeaders();
			respAr.status = req.status;
			respAr.statusText = req.statusText;
			// On IE some images were generating an XML error saying the responseText data was not yet available.
			try {
				respAr.responseText = req.responseText;
			}
			catch(e) {
				respAr.responseText = "unavailable";
			}
		}
		YSLOW.Component.handleResponse(respAr);
	}
}


YSLOW.Component.xmlCallbackError = function(details) {
	// The only time I saw this called was for http://news.yahoo.com/favicon.ico?.yslowt=1923847912384
	// In that case news.y.c was doing an infinite number of redirects and this was the only way to catch it.
	YSLOW.Component.handleResponse(details);
}


// Consume the XHR response.
YSLOW.Component.handleResponse = function(respAr) {
	var fetchedId = FirebugContext.yslowContext["xmlId"];
	FirebugContext.yslowContext["xmlId"] = "";
	var fetchedUrl = FirebugContext.yslowContext["xmlUrl"];
	FirebugContext.yslowContext["xmlUrl"] = "";
	var compObj = FirebugContext.yslowContext.getComponent(fetchedId);

	// Make sure we can find the component that initiated this XML request.
	if ( ! compObj ) {
		FirebugContext.yslowContext["fetchErrors"] += "ERROR: Failed to find component with id \"" + fetchedId + "\" for URL " + fetchedUrl + ".";
		FirebugContext.yslowContext.peelerObj.fetchNextComponentDetails();
		return;
	}

	compObj["fetch"] = "done";
	compObj["responsetime"] = FirebugContext.yslowContext["xmlEndtime"] - FirebugContext.yslowContext["xmlStarttime"];
	compObj.parseHeaders(respAr.responseHeaders);
    compObj.status = respAr.status;

	if ( 200 == respAr.status ) {
		// OK (normal) response
		var sType = YSLOW.Component.getCompVal(compObj, "type");
 		if ( "doc" == sType || "css" == sType || "js" == sType || "iframe" == sType || "xhr" == sType ) {
			// Save the responseText for the HTML document so we can parse it looking for CSS and JS locations.
			compObj["responseText"] = respAr.responseText;
		}

		// Special handling of text assets size.
		if ( "doc" == sType || "js" == sType || "css" == sType || "iframe" == sType || "xhr" == sType ) {
			if ( "gzip" == YSLOW.Component.getCompVal(compObj, "compressed") || "deflate" == YSLOW.Component.getCompVal(compObj, "compressed") ) {
				// If the doc was gzipped find the ungzipped size.
				compObj["ungzipped_size"] = respAr.responseText.length;
			}
			else if ( "" == YSLOW.Component.getCompVal(compObj, "content-length") ) {
				// If the doc was NOT gzipped and the content-length was missing, use the response length.
				compObj["content-length"] = respAr.responseText.length;
			}
		}
	}
	else if ( 301 == respAr.status || 302 == respAr.status ) {
		// Handle redirects - save it as a redirect object and create a new component for the actual object.
		var redirUrl = YSLOW.Component.getCompVal(compObj, "location");
		if ( redirUrl ) {
			// by passing in null for domObj we're not going to have some image info
			FirebugContext.yslowContext.peelerObj.addComponent(
				new YSLOW.Component(YSLOW.Component.getCompVal(compObj, "type"), redirUrl, FirebugContext.yslowContext.peelerObj) );
		}
		else {
			FirebugContext.yslowContext["fetchErrors"] += "ERROR: Found redirect with no location URL.";
		}
		compObj["type"] = "redirect";
	}
	else if ( 400 <= respAr.status && respAr.status <= 499 ) {
		if ( -1 != fetchedUrl.indexOf("about:blank") ) {
			// do nothing - probably Ajax calls in the html doc
		}
		else {
			FirebugContext.yslowContext["fetchErrors"] += 'Request for "' + YSLOW.Component.getCompVal(compObj, "url") + '" failed. Bad URL?\n';
		}
	}
	else {
		FirebugContext.yslowContext["fetchErrors"] += 'Request for "' + YSLOW.Component.getCompVal(compObj, "url") + '" failed with status ' + respAr["status"] + '. Bad URL?\n';
	}

	FirebugContext.yslowContext.peelerObj.fetchNextComponentDetails();  // this keeps the iteration going
}


// Fill the component's values that derive from the HTTP response.
YSLOW.Component.prototype.setComponentDetails = function(status, responseHeaders, responseText, responsetime) {
	this["responsetime"] = responsetime;
	this.parseHeaders(responseHeaders);

	if ( 200 == status ) {
		// OK (normal) response
		var sType = this["type"];
 		if ( "doc" == sType || "css" == sType || "js" == sType || "iframe" == sType || "xhr" == sType ) {
			// Save the responseText for the HTML document so we can parse it looking for CSS and JS locations.
			this["responseText"] = responseText;
		}

		// Special handling of text assets size.
		if ( "doc" == sType || "js" == sType || "css" == sType || "iframe" == sType || "xhr" == sType ) {
			if ( "gzip" == this["compressed"] || "deflate" == this["compressed"] ) {
				// If the doc was gzipped find the ungzipped size.
				this["ungzipped_size"] = responseText.length;
			}
			else if ( "" == this["content-length"] ) {
				// If the doc was NOT gzipped and the content-length was missing, use the response length.
				this["content-length"] = responseText.length;
			}
		}
	}
	else if ( 301 == status || 302 == status ) {
		this["type"] = "redirect";
	}
}


// Parse the HTTP response headers and add them to the component object.
YSLOW.Component.prototype.parseHeaders = function(sHeaders) {
	this["headers"] = sHeaders;

	// We MUST have these values in the object, so initialize them.
	this["content-length"] = 0;
	this["last-modified"] = "";
	this["content-type"] = "";
	this["compressed"] = "";
	this["etag"] = "";
	this["expires"] = "";
	this["redir"] = 0;

	var headerAr = sHeaders.split("\n");              // the header is line separated
	for ( var ii=0; ii < headerAr.length; ii++ ) {
		var respitem = headerAr[ii];
		// Blank responses lines are a possibility. Ignore those.
		if ( respitem.match(/^\S+:.*$/) ) {
			var item = respitem.split(":");
			var hdrname = item[0].toLowerCase();
			item.shift();
			if ( 0 == item.length ) {
				this[hdrname] = "";
			}
			else {
				var hdrValue = item.join(":");
				// Remove leading whitespace.
				while ( " " == hdrValue.substring(0, 1) ) {
					hdrValue = hdrValue.substring(1);
				}
				this[hdrname] = hdrValue;
			}
			if ( hdrname == "content-encoding" && item[0].toLowerCase().match(/.*gzip.*/) ) {
				this['compressed'] = "gzip";
			}
			else if ( hdrname == "content-encoding" && item[0].toLowerCase().match(/.*deflate.*/) ) {
				this['compressed'] = "deflate";
			}
		}
	}
}


YSLOW.Component.prototype.isMinified = function() {
	if ( "undefined" == typeof(this.bMinified) ) {
		// For efficiency, only call the expensive isMinified function once.
		this.bMinified = YSLOW.Component.isMinified(this.responseText);
	}

	return this.bMinified;
}


// Return true if the component is minified. Intended only for JavaScript files.
// We make this a static function so it can
YSLOW.Component.isMinified = function(responseText) {
	if ( ! responseText ) {
		return true;
	}

	var numComments = 0;
	// Arbitrarily pick a number of comments that indicates not minimized.
	var maxComments = 3;

	// Count /* */ comments. Extract the comments while we do that (because they often
    // contain the bad strings that we look for next).
	var iStart;
	var tmpText = responseText;  // remove comments from here
	var newText = "";            // add clean text to this
	if ( -1 == tmpText.indexOf("/*") ) {
		newText = tmpText;
	}
	else {
		while ( -1 != ( iStart = tmpText.indexOf("/*") ) ) {
			var iEnd = tmpText.indexOf("*/", iStart);
			if ( -1 == iEnd ) {
				// This is just a bug somewhere. We found the start but not the end of a comment.
				newText += tmpText;
				break;
			}
			else {
				var sComment = tmpText.substring(iStart, iEnd+2);
				newText += tmpText.substring(0, iStart);
				tmpText = tmpText.substring(iEnd+2);
				if ( 0 == tmpText.indexOf("\n") ) {
					// After removing the comment if we left a carriage return, remove it.
					tmpText = tmpText.substring(1);
				}
				if ( -1 == sComment.toLowerCase().indexOf("copyright") ) {
					dprint(5, "Found comment: " + sComment);
					numComments++;
					if ( numComments >= maxComments ) {
						return false;  // quick exit - if we get too many comments return immediately.
					}
				}
			}
		}
	}

	// Count strings (like carriage return) that indicate NOT minified.
	var aBadStrings = ["\n//", "\n ", "\n\t", " // ", "\n\n"];
	for (var i = 0; i < aBadStrings.length; i++) {
		var sBad = aBadStrings[i];
		// Count the number of times this bad string appears in the response text.
		var iComment = newText.indexOf(sBad);
		while ( -1 != iComment ) {
			dprint(5, "Found comment for bad string: '" + sBad + "'.");
			numComments++;
			if ( numComments >= maxComments ) {
				return false;  // quick exit - if we get too many comments return immediately.
			}
			iComment = newText.indexOf(sBad, iComment+1);
		}
	}

	return true;
}


YSLOW.Component.prototype.expressionCount = function() {
	if ( "undefined" == typeof(this.numExpressions) ) {
		// For efficiency, only call the expensive parseExpressions function once.
		this.numExpressions = YSLOW.Component.parseExpressions(this.responseText);
	}

	return this.numExpressions;
}


// Given the contents of a stylesheet (as a string) parse it and return
// the number of times "expression(" is used.
// This can be inaccurate (if "expression(" is used in some weird way, even in comments),
// but we CAN'T crawl the DOM and look at cssRule objects because Firefox will have
// stripped out all the "expression" rules. So we have to parse text.
YSLOW.Component.parseExpressions = function(cssText) {
	var numExpr = 0;

	// In my testing there could NOT be a space between "expression" and "(".
	var iExpr = cssText.indexOf("expression(");
	while ( -1 != iExpr ) {
		numExpr++;
		iExpr = cssText.indexOf("expression(", iExpr+1);
	}

	return numExpr;
}


// Return true if this object has a far future Expires.
// @todo: make the "far" interval configurable
YSLOW.Component.hasFarFutureExpiresOrMaxAge = function(compObj) {
	var now = Number(new Date());
	var expires = YSLOW.Component.getCompVal(compObj, "expires");
	var minSeconds = YSLOW.getPref("yslow.minFutureExpiresSeconds", 2*24*60*60);
	var minMilliSeconds = minSeconds * 1000;

	if ( (YSLOW.Utils.epochMS(expires) - now) > minMilliSeconds ) {
		return true;
	}
	else {
		var maxageMS = YSLOW.Component.getMaxAge(compObj) * 1000;
		if ( maxageMS > minMilliSeconds ) {
			return true;
		}
		return false;
	}
}


// If there's a "Cache-Control: max-age=nseconds" header return the number of seconds.
// Ow return -1.
YSLOW.Component.getMaxAge = function(compObj) {
	var cachecontrol = YSLOW.Component.getCompVal(compObj, "cache-control");
	if ( cachecontrol ) {
		var i1 = cachecontrol.indexOf("max-age=");
		if ( -1 != i1 ) {
			return parseInt(cachecontrol.substring(i1+8));
		}
	}

	return -1;
}


// Return true if this object has a last-modified date significantly in the past.
YSLOW.Component.hasOldModifiedDate = function(compObj) {
	var now = Number(new Date());
	var modified_date = YSLOW.Component.getCompVal(compObj, "last-modified");
	return ( (now - YSLOW.Utils.epochMS(modified_date)) > (24*60*60*1000) );  // at least 1 day in the past
}

// Return true if this component is violating ETag guidelines.
// If the Server is Apache and the ETag format is "blah-blah-blah" (eg: "424510-11f1-8b23ddc0" ) that's probably bad, because the middle one is the inode.
// If the Server is IIS and the ETag format is "blah:blah" (eg: "80dfc0e28cbc71:83a") that's probably bad, because the second one is the ChangeNumber, which is
// unique across each server.
YSLOW.Component.hasBadEtag = function(compObj) {
	//eprint("Into Component.hasBadEtag");

	var etag = YSLOW.Component.getCompVal(compObj, "etag");
	var serverInfo = YSLOW.Component.getCompVal(compObj, "server");

	if ( "" != etag ) {
		//Eliminating the quotes.
		etag = etag.substring(etag.indexOf('"')+1,etag.length-1);

		if ( "undefined" != typeof(serverInfo) ) {
			var apacheFormat = new RegExp("^[0-9a-z]+-[0-9a-z]+-[0-9a-z]+$");
			var iisFormat = new RegExp("^[0-9a-z]+:[0-9a-z]+$");

			if ( (serverInfo.indexOf("Apache") != -1 ) && etag.match(apacheFormat) ) {
				//eprint("Server is Apache :"+serverInfo+" Etag:"+etag);
				return true;
			}
			else if ( (serverInfo.indexOf("IIS") != -1 ) && etag.match(iisFormat) ) {
				//eprint("Server is IIS :"+serverInfo+" Etag:"+etag);
				return true;
			}
			else {
				return false;
			}
		}
		return false;
	}
	return false;
}





// Build a string for outputting a component.
YSLOW.Component.printComponent = function(compObj, i) {
    //dprint(9, "printComponent: enter function");

	var sColor = ( 0 == i % 2 ? ' bgcolor=#FFFFFF' : ' bgcolor=#EFEFEF' );
	var	sResults = "";

	var headersDivId = "headerdiv_" + i;
	for ( var iColumn = 0; iColumn < YSLOW.Component.columns_fields.length; iColumn++ ) {
		var sColumn = YSLOW.Component.columns_fields[iColumn];
		var val = YSLOW.Component.getCompVal(compObj, sColumn);
        var iStatus = parseInt(compObj.status, 10);

		var sClass = "reportcardmiddle";
		if ( 0 == iColumn )
			sClass = "reportcardmiddle";
		else if ( iColumn+1 == YSLOW.Component.columns_fields.length )
			sClass = "reportcardright";
		if ( "url" == sColumn )
			sClass += " noline";
		else
			sClass += " rule";

		// Big if else block
		var sAlign = "left";
		if ( "type" == sColumn ) {
			sAlign = "center";
		}
		else if ( "url" == sColumn ) {
			sAlign = "left";
			val = "<nobr>" + YSLOW.Utils.getHeaderImageEmbedCode(headersDivId) +
				  YSLOW.Utils.prettyAnchor(val, val, ( YSLOW.Component.getCompVal(compObj, "error") ? "warning" : "compsAnchor" ), !FirebugContext.yslowContext["bPrintable"], undefined, undefined, compObj.type) + "</nobr>";
		}
		else if ( "expires" == sColumn ) {
			sAlign = "right";
			if ( "" != val ) {
				val = YSLOW.Utils.prettyExpiresDate(val);
			}
		}
		else if ( "compressed" == sColumn ) {
			sAlign = "center";
		}
		else if ( "responsetime" == sColumn ) {
			sAlign = "right";
		}
		else if ( "content-length" == sColumn ) {
			sAlign = "right";
			val = ( val == "" ? "" : YSLOW.Utils.kbSize(val) );
			if ( ( "gzip" == YSLOW.Component.getCompVal(compObj, "compressed") || "deflate" == YSLOW.Component.getCompVal(compObj, "compressed") )
				 && YSLOW.Component.getCompVal(compObj, "ungzipped_size") ) {
				// If the object was gzipped, print the ungzipped size (if available).
				val = "<nobr>" + val + " (" + YSLOW.Utils.kbSize(YSLOW.Component.getCompVal(compObj, "ungzipped_size")) + ")</nobr>";
			}
		}

		if ( "" == val ) {
			val = '&nbsp;';
		}

        if (typeof compObj.status != "undefined" && iStatus > 399 && iStatus < 500) {
            if (sColumn == 'url') {
                val += ' ('+ iStatus +')';
            }
            val = '<span class="compNotFound">' + val + '</span>';
        }

		sResults += '    <td class="' + sClass + '" style="border-top: 0px;" align=' + sAlign + '>' + val + '</td>\n';

		if ( "url" == sColumn && YSLOW.Component.getCompVal(compObj, "error") ) {
			sResults += '<td class=reportcardright style="border-top: 0px;" colspan=5 align=center><font color=CC0000>' +
						YSLOW.Component.getCompVal(compObj, "error") + '</font></td>';
			break;
		}
	}
	sResults = ' <tr' + sColor + '>' + sResults + ' </tr><tr' + sColor + '><td colspan=7><div id=' + headersDivId + ' style="display: none;">' +
			   YSLOW.Component.printComponentHeaders(compObj) + '</div></td></tr>';

	return sResults;
}


YSLOW.Component.printComponentHeaders = function(compObj) {
var stext =
'<table class="netTable" cellpadding="0" cellspacing="0">' +
'<tr class="netInfoRow">' +
'<td colspan="4" class="netInfoCol" style="background-image: none; background-color: #BBB;">' +
'<div class="netInfoBody">' +
'<div class="netInfoTabs">' +
'<a view="Params" class="netInfoParamsTab netInfoTab collapsed">' +
'Params</a>' +
'<a selected="true" view="Headers" class="netInfoHeadersTab netInfoTab">' +
'Headers</a>' +
'<a view="Post" class="netInfoPostTab netInfoTab collapsed">' +
'Post</a>' +
'<a view="Response" class="netInfoResponseTab netInfoTab">' +
'</a>' +
'</div>' +
'<table class="netInfoParamsText netInfoText netInfoParamsTable" cellpadding="0" cellspacing="0">' +
'<tbody class="">' +
'</tbody>' +
'</table>' +
'<table selected="true" class="netInfoHeadersText netInfoText netInfoHeadersTable" cellpadding="0" cellspacing="0">' +
'<tbody class="">' +
'<tr class="netInfoResponseHeadersTitle">' +
'<td colspan="2" class="">' +
'<div class="netInfoHeadersGroup">' +
'Response Headers</div>' +
'</td>' +
'</tr>' +

YSLOW.Component.getHeadersFF(compObj) +

'</tbody>' +
'</table>' +
'<div class="netInfoPostText netInfoText">' +
'<table class="netInfoPostTable" cellpadding="0" cellspacing="0">' +
'<tbody class="">' +
'</tbody>' +
'</table>' +
'</div>' +
'<div class="netInfoResponseText netInfoText">' +
'Loading...</div>' +
'</div>' +
'</td>' +
'</tr>' +
'</table>' +
'';

return stext;
}


YSLOW.Component.getHeadersFF = function(compObj) {
	var sHeaders = "ERROR: Unable to find headers for component";
	if ( compObj ) {
		var headerAr = YSLOW.Component.getCompVal(compObj, "headers").split("\n");              // the header is line separated
		for (var i = 0; i < headerAr.length; i++) {
			var sHeader = headerAr[i];
			var iColon = sHeader.indexOf(":");
			if ( -1 != iColon ) {
sHeader = '<tr class=""><td class="netInfoParamName">' + sHeader.substring(0, iColon+1) + '</td><td class="netInfoParamValue">' +
	sHeader.substring(iColon+1) + '</td></tr>';
				headerAr[i] = sHeader;
			}
		}
		sHeaders = headerAr.join("");
	}

	return sHeaders;
}

